iT邦幫忙

0

[Day4]小型爬蟲:抓公開頁面的標題與連結

  • 分享至 

  • xImage
  •  

今天要做什麼
給一個起始網址(landing page),把頁面上可見的 文字與 href 連結抓下來:
指定最大筆數(避免一次抓太多)
僅保留同網域(或允許多個網域的白名單)
延遲請求(避免過度頻繁)
可輸出成 CSV

會用到
requests:發送 HTTP
bs4 (BeautifulSoup):解析 HTML
urllib.parse:處理網址 & 網域比對
argparse:命令列參數
csv:輸出結果

第一次用請先安裝:
pip install requests beautifulsoup4

程式碼(crawl_titles.py)
把下面存成 crawl_titles.py 放在project 資料夾

import argparse, csv, time, sys
from urllib.parse import urljoin, urlparse
import requests
from bs4 import BeautifulSoup

def same_domain(url: str, allowed: set[str]) -> bool:
    netloc = urlparse(url).netloc.lower()
    return any(netloc == d or netloc.endswith("." + d) for d in allowed)

def fetch(url: str, timeout=10):
    headers = {
        "User-Agent": "Mozilla/5.0 (compatible; TinyCrawler/1.0; +https://example.local)"
    }
    r = requests.get(url, headers=headers, timeout=timeout)
    r.raise_for_status()
    return r.text

def extract_links(html: str, base_url: str):
    soup = BeautifulSoup(html, "html.parser")
    results = []
    for a in soup.select("a[href]"):
        text = (a.get_text() or "").strip()
        href = a.get("href")
        if not text or not href:
            continue
        abs_url = urljoin(base_url, href)
        results.append((text, abs_url))
    return results

def main():
    ap = argparse.ArgumentParser(description="抓取頁面上的標題與連結")
    ap.add_argument("url", help="起始網址,例如:https://ithelp.ithome.com.tw/")
    ap.add_argument("--allow", nargs="*", default=[], help="允許的網域(空白分隔),例如 ithelp.ithome.com.tw ithome.com.tw")
    ap.add_argument("--limit", type=int, default=100, help="最多輸出筆數(預設 100)")
    ap.add_argument("--delay", type=float, default=1.0, help="請求延遲秒數(預設 1.0)")
    ap.add_argument("--out", help="輸出 CSV 檔名(不指定則只印出來)")
    args = ap.parse_args()

    start = args.url
    allowed = set(args.allow) if args.allow else {urlparse(start).netloc.lower()}

    try:
        print(f"Fetching: {start}")
        html = fetch(start)
        time.sleep(args.delay)  # 禮貌延遲
    except requests.RequestException as e:
        print("❌ 請求失敗:", e)
        sys.exit(1)

    pairs = extract_links(html, start)

    # 過濾:只保留允許網域、http(s)、去重
    seen = set()
    cleaned = []
    for text, link in pairs:
        if not link.startswith(("http://", "https://")):
            continue
        if not same_domain(link, allowed):
            continue
        key = (text, link)
        if key in seen:
            continue
        seen.add(key)
        cleaned.append({"text": text[:200], "url": link})
        if len(cleaned) >= args.limit:
            break

    if args.out:
        with open(args.out, "w", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=["text", "url"])
            w.writeheader()
            w.writerows(cleaned)
        print(f"✅ 已輸出 CSV:{args.out}({len(cleaned)} 筆)")
    else:
        for i, row in enumerate(cleaned, 1):
            print(f"{i:>3}. {row['text']} - {row['url']}")
        print(f"✅ 總筆數:{len(cleaned)}")

if __name__ == "__main__":
    main()

執行範例
只看輸出python crawl_titles.py https://ithelp.ithome.com.tw/ --limit 10 --delay 0.5
限制在指定網域,並輸出 CSVpython crawl_titles.py https://ithelp.ithome.com.tw/ --allow ithelp.ithome.com.tw --limit 50 --out links.csv
換個網站(記得改 allow 網域)python crawl_titles.py https://www.python.org/ --allow python.org --limit 40 --out py_links.csv

實作:
https://ithelp.ithome.com.tw/upload/images/20250923/20169368ZbMfyHPbDC.png
合法爬蟲禮節(最小清單)
尊重 robots.txt:先用瀏覽器看 https://網站域名/robots.txt,確認允許抓的路徑。
加上 User-Agent:程式碼已放入基本 UA。
限速:加入 --delay;大量頁面就提高延遲。
小量測試 → 再擴大:先 --limit 小一點、或先不輸出檔。
只抓公開資訊:不要登入、不要繞過驗證、不要抓個資。

今日重點
Requests + BeautifulSoup 能快速完成單頁資訊擷取
用 urlparse 做網域白名單,避免不小心外連太多
先設 limit、delay 與 allow 是安全穩妥的預設
需要可重複利用的話,直接用 --out 產 CSV

明天我們會做一個 CSV ↔ JSON 轉換器,把資料在兩種格式間快速互轉,並處理常見的編碼/欄位型別問題。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言